package com.tc.nio.c4;

import com.tc.util.ByteBufferUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * @Auther: tianchao
 * @Date: 2022/2/6 12:06
 * @Description:
 */
@Slf4j
public class SelectorServer {
    public static void main(String[] args) throws IOException {
        // 1 创建selector 管理多个channel
        Selector selector = Selector.open();

        ByteBuffer buffer = ByteBuffer.allocate(16);
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);

        // 2 注册 调用channel的方法 建立selector和channel联系  selectionKey:事件发生时,哪个channel放生的事件
        //事件类型  accept  / connect / read / write
        /**
         * accept: 会在有连接请求的时候触发 - 针对服务端
         * connect: 连接建立后触发的事件 - 针对客户端
         * read: 有数据了,可读
         * write: 可写事件
         */
        SelectionKey serverSocketChannelKey = serverSocketChannel.register(selector, 0, null);
        // 设置关注的事件
        // 关注accept事件
        serverSocketChannelKey.interestOps(SelectionKey.OP_ACCEPT);
        log.info("register key : {}", serverSocketChannelKey);
        serverSocketChannel.bind(new InetSocketAddress(8080));
        while (true){
            // 3 selector方法  select()方法没有事件的时候是阻塞的,有事件发生,线程才会继续运行，循环空转问题
            // 当事件没有被处理的时候,不会阻塞 selector.select();
            selector.select();
            // 4 处理事件
            // 所有可用的事件 selectionKeys包含了发生的事件集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                //非常重要 自己移除 否则下次循环 accpet()方法 返回null 报空指针
                iterator.remove();
                log.debug("event happened  key : {}", key);
                // 5 区分事件类型
                if ( key.isAcceptable()){
                    ServerSocketChannel channel = (ServerSocketChannel)key.channel();
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    final SelectionKey socketChannelKey = sc.register(selector, 0, null);
                    // 关注读
                    socketChannelKey.interestOps(SelectionKey.OP_READ);
                    log.debug("{}", sc);
                } else if (key.isReadable()){
                    try {
                        SocketChannel channel = (SocketChannel)key.channel();
                        //TODO 局部变量有问题，消息大于20的时候 触发两次 read事件， 导致下面的逻辑是不正确的,
                        // 但是如果设置成全局，又会被多个SocketChannel共享,最好的结果应该是每个SocketChannel有自己的ByteBuffer
                        // 参考本包下的类 com.tc.netty.c4.SelectorServer2
                        ByteBuffer byteBufferTemp = ByteBuffer.allocate(20);
                        final int read = channel.read(byteBufferTemp);
                        //read 是 -1的情况下 是正常断开
                        if (read == -1 ){
                            key.cancel();
                            log.debug("正常断开 sc channel : {}",channel);
                        }else {
                            split(byteBufferTemp);
                            //byteBufferTemp.flip();
                            //ByteBufferUtil.debugRead(byteBufferTemp);
                            //log.debug("buffer内容: {}", Charset.defaultCharset().decode(byteBufferTemp));
                            //byteBufferTemp.clear();
                        }
                    }catch (IOException e){
                        e.printStackTrace();
                        //因为客户端断开了，因此需要将key取消（从selector的key中真正删除）
                        key.cancel();
                    }
                }
                // 事件取消了 key.cancel(); selector.select(); 仍然会阻塞 相当于被处理了 处理过程是取消
            }
        }
    }
    private static void split(ByteBuffer source) {
        source.flip();
//        log.info("接收到的消息 :{}",Charset.defaultCharset().decode(source));

        for (int i = 0; i < source.limit(); i++) {
            final byte b = source.get(i);
            if (b == '\n'){
                int length = i + 1 - source.position();
                ByteBuffer target = ByteBuffer.allocate(length);
                //向source读  向target写
                for (int j = 0; j < length; j++) {
                    target.put(source.get());
                }
                ByteBufferUtil.debugAll(target);
            }
        }
        // 写 不能从头写 source.clear()；不对
        source.compact();
    }
}
